using System;
using System.Reflection;
using System.Collections;
using System.Runtime.Remoting.Messaging;
using System.IO;

using Evaluant.Uss.ObjectContext.DynamicProxy;
using Evaluant.Uss.ObjectContext.Descriptors;
using Evaluant.Uss.MetaData;

namespace Evaluant.Uss.ObjectContext
{
	/// <summary>
	/// 
	/// </summary>
	/// <remarks>
	/// This object is Thread Safe.
	/// </remarks>
	public class ObjectService
	{

		private string _Slot;
		private Hashtable _TypeChache = new Hashtable(20);
		private string _DefaultNamespace = String.Empty;
		private IPersistenceProvider _PersistenceEngineFactory;
		private IPersistableProxyFactory _ProxyFactory;
		private IPersistentDescriptor _Descriptor;
		private ArrayList _MetaData;

		private object _SynLock = new object();
		
		/// <summary>
		/// Creates a new <see cref="ObjectContext"/> instance.
		/// </summary>
		/// <param name="engineFactory">A persistence engine factory.</param>
		public ObjectService(IPersistenceProvider engineFactory)
		{
			if(engineFactory == null)
				throw new ArgumentNullException("engineFactory");

			_PersistenceEngineFactory = engineFactory;

			_Descriptor = new ReflectionDescriptor();
			_ProxyFactory = new PersistableProxyFactory(_Descriptor, _PersistenceEngineFactory.CreatePersistenceEngine().Model);
			_MetaData = new ArrayList(50);

			// Makes it unique as many ObjectService could be used in the same application
			_Slot = Guid.NewGuid().ToString();
		}

		/// <summary>
		/// Creates a new <see cref="ObjectService"/> instance.
		/// </summary>
		/// <param name="filename">Filename.</param>
		public ObjectService(string filename) : this(XmlConfigLoader.LoadXmlConfig(filename))
		{	
		}

		/// <summary>
		/// Creates a new <see cref="ObjectService"/> instance.
		/// </summary>
		/// <param name="stream">A stream containing the document to load.</param>
		public ObjectService(Stream stream) : this(XmlConfigLoader.LoadXmlConfig(stream, null))
		{	
		}

		/// <summary>
		/// Creates a new <see cref="ObjectService"/> instance.
		/// </summary>
		/// <param name="stream">A stream containing the document to load.</param>
		/// <param name="engineName">Name of the engine.</param>
		public ObjectService(Stream stream, string engineName) : this(XmlConfigLoader.LoadXmlConfig(stream, engineName))
		{
		}

		/// <summary>
		/// Creates a new <see cref="ObjectService"/> instance.
		/// </summary>
		/// <param name="filename">Filename.</param>
		/// <param name="engineName">Name of the engine.</param>
		public ObjectService(string filename, string engineName) : this(XmlConfigLoader.LoadXmlConfig(filename, engineName))
		{
		}

        /// <summary>
        /// Creates a new <see cref="ObjectService"/> instance.
        /// </summary>
        /// <param name="assembly">The assembly conaining the resource.</param>
        /// <param name="resource">Resource representing the configuration file.</param>
        public ObjectService(Assembly assembly, string resource)
            : this(XmlConfigLoader.LoadXmlConfig(assembly, resource, null))
        {
        }

        /// <summary>
        /// Creates a new <see cref="ObjectService"/> instance.
        /// </summary>
        /// <param name="assembly">The assembly conaining the resource.</param>
        /// <param name="resource">Resource representing the configuration file.</param>
        /// <param name="engineName">Name of the engine.</param>
        public ObjectService(Assembly assembly, string resource, string engineName)
            : this(XmlConfigLoader.LoadXmlConfig(assembly, resource, engineName))
        {
        }
        
        /// <summary>
		/// Gets or sets the persistence engine factory.
		/// </summary>
		/// <value></value>
		public IPersistenceProvider PersistenceEngineFactory
		{
			get { return _PersistenceEngineFactory; }
			set { _PersistenceEngineFactory = value; }
		}

		/// <summary>
		/// Gets the persistable proxy factory.
		/// </summary>
		/// <value>The persistable proxy factory.</value>
		public IPersistableProxyFactory PersistableProxyFactory
		{
			get { return _ProxyFactory; }
		}

		/// <summary>
		/// Gets or sets the persistent descriptor.
		/// </summary>
		/// <value>The persistent descriptor.</value>
		public IPersistentDescriptor PersistentDescriptor
		{
			get { return _Descriptor; }
			set
			{
				_ProxyFactory = new PersistableProxyFactory(value, _PersistenceEngineFactory.CreatePersistenceEngine().Model);
				_Descriptor = value;
			}
		}

		/// <summary>
		/// Gets or sets the default namespace in OPath queries.
		/// </summary>
		/// <value></value>
		public string DefaultNamespace
		{
			get { return _DefaultNamespace; }
			set { _DefaultNamespace = value; }
		}

		/// <summary>
		/// Get a domain Type given an entity's type
		/// </summary>
		/// <param name="entityType">A full type of an entity</param>
		/// <returns>The corresponding CLR Type</returns>
		public Type GetDomainType(string entityType)
		{
			// Locks the access to the method in case concurrent threads access this method (ASP.Net)
			lock(_SynLock)
			{
				// Search in the Type cache
				if(_TypeChache.Contains(entityType))
					return (Type)_TypeChache[entityType];

				Type type = null;
				string fullname = ObjectContext.ConvertNamespaceEussToDomain(entityType);

				// Search in the registered assemblies
				foreach(Assembly assembly in _CachedAssemblies)
				{
					type = assembly.GetType(fullname);
					if(type != null)
						break;
				}

				if(type != null)
					_TypeChache.Add(entityType, type);

				return type;
			}
		}

		protected ArrayList _CachedAssemblies = new ArrayList();
        
		/// <summary>
		/// Registers an Assembly which contains type definitions
		/// </summary>
		/// <param name="assemblyname">The name of the assembly to add</param>
		public void AddAssembly(string assemblyname)
		{
			if(assemblyname == null)
				throw new ArgumentNullException("assemblyname");

			AddAssembly(Assembly.Load(assemblyname));
		}

		/// <summary>
		/// Registers an Assembly which contains type definitions
		/// </summary>
		/// <param name="assembly">The assembly to add</param>
		public void AddAssembly(Assembly assembly)
		{
			lock(_SynLock)
			{
				if(assembly == null || _CachedAssemblies.Contains(assembly))
					return;
			
				_CachedAssemblies.Insert(0, assembly);
			}
		}

		/// <summary>
		/// Creates a new persistence manager.
		/// </summary>
		/// <returns></returns>
		public ObjectContext CreateObjectContext()
		{
			lock(_SynLock)
			{
				return new ObjectContext(this, PersistenceEngineFactory.CreatePersistenceEngine());
			}
		}

		/// <summary>
		/// Gets the current persistence manager.
		/// </summary>
		/// <value></value>
		public ObjectContext CurrentObjectContext
		{
			get 
			{ 
				lock(_SynLock)
				{
					ObjectContext _Instance = CallContext.GetData(_Slot) as ObjectContext; 
				
					if(_Instance == null)
					{
						CallContext.SetData(_Slot, _Instance = CreateObjectContext());
					}

					return _Instance;
				}
			}
		}

		/// <summary>
		/// Registers the meta data.
		/// </summary>
		/// <param name="metadata">The metadata.</param>
		public void RegisterMetaData(IMetaData metadata)
		{
			RegisterMetaData(new IMetaData[] { metadata });
		}

		/// <summary>
		/// Registers the meta data.
		/// </summary>
		/// <param name="metadata">The metadata.</param>
		public void RegisterMetaData(IMetaData[] metadata)
		{
			_PersistenceEngineFactory.RegisterMetaData(metadata);
		}

        public static void ImportRepository(ObjectContext source, ObjectContext target, bool bulk)
        {
            XmlConfigLoader.ImportRepository(source.PersistenceEngine, target.PersistenceEngine, bulk);
        }
	}
}
